﻿using DevExpress.Drawing;
using DevExpress.ExpressApp.Editors;
using DevExpress.Persistent.Base;
using DevExpress.Xpo;
using EasyXaf.EntityDesigner.BusinessObjects;
using EasyXaf.EntityDesigner.BusinessObjects.Entities;
using EasyXaf.EntityDesigner.BusinessObjects.Entities.Appearances;
using EasyXaf.EntityDesigner.BusinessObjects.Entities.Associations;
using EasyXaf.EntityDesigner.BusinessObjects.Entities.EntityProperties;
using EasyXaf.EntityDesigner.BusinessObjects.Entities.ValidationRules;
using EasyXaf.EntityDesigner.CodeTemplates.Extensions;
using Microsoft.CodeAnalysis;
using System.Text;

namespace EasyXaf.EntityDesigner.CodeTemplates;

public class XpoEntityTemplate : TemplateBase
{
    private IEnumerable<EntityProperty> _entityProperties;

    protected IEnumerable<EntityProperty> EntityProperties
    {
        get
        {
            if (_entityProperties != null)
            {
                return _entityProperties;
            }

            var properties = Entity.Properties.OrderBy(p => p.Order).ToList();

            var associations = Entity.DomainModel.Entities
                .SelectMany(x => x.Associations)
                .Where(x => (x.Parent == Entity && x.ParentProperty == null) || (x.Child == Entity && x.ChildProperty == null))
                .ToList();

            foreach (var association in associations)
            {
                if (association.Parent == Entity && association.ParentProperty == null)
                {
                    var propertyName = association.ParentPropertyName;
                    if (!string.IsNullOrWhiteSpace(propertyName))
                    {
                        properties.Add(new AssociationProperty(Entity.Session)
                        {
                            Name = propertyName,
                            PropertyType = association.AssociationType == AssociationType.OneToOne
                                ? PropertyType.Entity
                                : PropertyType.EntityCollection,
                            EntityType = association.Child,
                            Entity = Entity,
                            Association = association
                        });
                    }
                }

                if (association.Child == Entity && association.ChildProperty == null)
                {
                    var propertyName = association.ChildPropertyName;
                    if (!string.IsNullOrWhiteSpace(propertyName))
                    {
                        properties.Add(new AssociationProperty(Entity.Session)
                        {
                            Name = propertyName,
                            PropertyType = association.AssociationType == AssociationType.ManyToMany
                                ? PropertyType.EntityCollection
                                : PropertyType.Entity,
                            EntityType = association.Parent,
                            Entity = Entity,
                            Association = association
                        });
                    }
                }
            }

            return _entityProperties = properties;
        }
    }

    public Entity Entity { get; }

    public XpoEntityTemplate(Entity entity)
    {
        Entity = entity;
    }

    private static Association GetAssociation(EntityProperty entityProperty)
    {
        if (entityProperty.PropertyType != PropertyType.Entity && entityProperty.PropertyType != PropertyType.EntityCollection)
        {
            return null;
        }

        if (entityProperty is AssociationProperty associationProperty)
        {
            return associationProperty.Association;
        }

        var association = entityProperty.Entity.Associations
            .Where(x => x.ParentProperty == entityProperty || x.ChildProperty == entityProperty)
            .FirstOrDefault();

        return association ?? entityProperty.EntityType?.Associations
            .Where(x => x.ParentProperty == entityProperty || x.ChildProperty == entityProperty)
            .FirstOrDefault();
    }

    private static IEnumerable<string> GetPropertyTypeNamespaces(EntityProperty entityProperty)
    {
        var namespaces = new List<string>();

        if (entityProperty.PropertyType == PropertyType.Enumeration)
        {
            if (entityProperty.EnumType != null)
            {
                namespaces.Add(entityProperty.EnumType.GetNamespace());
            }
            else if (!string.IsNullOrWhiteSpace(entityProperty.EnumTypeFullName))
            {
                namespaces.Add(string.Join('.', entityProperty.EnumTypeFullName.Split('.')[..^1]));
            }
        }
        else if (entityProperty.PropertyType == PropertyType.Entity)
        {
            namespaces.Add(entityProperty.EntityType?.GetNamespace());
        }
        else if (entityProperty.PropertyType == PropertyType.EntityCollection)
        {
            namespaces.Add(entityProperty.EntityType?.GetNamespace());
        }

        return namespaces
            .Where(n => !string.IsNullOrWhiteSpace(n))
            .Distinct()
            .OrderBy(n => n);
    }

    private Dictionary<string, string> GetEntityAttributes(out List<string> namespaces)
    {
        var attributes = new Dictionary<string, string>();

        namespaces = [];

        if (!string.IsNullOrWhiteSpace(Entity.Caption))
        {
            var name = "XafDisplayName";
            attributes[name] = $"{name}(\"{Entity.Caption}\")";
            namespaces.Add("DevExpress.ExpressApp.DC");
        }

        if (!string.IsNullOrWhiteSpace(Entity.CaptionFormat))
        {
            var name = "ObjectCaptionFormat";
            attributes[name] = $"{name}(\"{Entity.CaptionFormat}\")";
            namespaces.Add("DevExpress.Persistent.Base");
        }

        if (!string.IsNullOrWhiteSpace(Entity.Description))
        {
            var name = "Description";
            attributes[name] = $"{name}(\"{Entity.Description.ToSingleLine()}\")";
            namespaces.Add("System.ComponentModel");
        }

        if (!string.IsNullOrWhiteSpace(Entity.ImageName))
        {
            var name = "ImageName";
            attributes[name] = $"{name}(\"{Entity.ImageName}\")";
            namespaces.Add("DevExpress.Persistent.Base");
        }

        if (Entity.DefaultProperty != null)
        {
            var name = "XafDefaultProperty";
            attributes[name] = $"{name}(nameof({Entity.DefaultProperty.Name}))";
            namespaces.Add("DevExpress.ExpressApp.DC");
        }

        if (Entity.IsCloneable != DefaultBoolean.Default)
        {
            var name = "ModelDefault";
            attributes[$"{name}-IsCloneable"] = $"{name}(\"IsCloneable\", \"{Entity.IsCloneable.GetString()}\")";
            namespaces.Add("DevExpress.ExpressApp.Model");
        }

        if (Entity.IsCreatable != DefaultBoolean.Default)
        {
            var name = "CreatableItem";
            attributes[name] = Entity.IsCreatable == DefaultBoolean.True ? name : $"{name}({Entity.IsCreatable.GetString()})";
            namespaces.Add("DevExpress.Persistent.Base");
        }

        if (Entity.IsVisibleInNavigations != DefaultBoolean.Default)
        {
            var name = "NavigationItem";
            if (Entity.IsVisibleInNavigations == DefaultBoolean.True)
            {
                var category = Entity.Category;
                category = category == NavigationItemAttribute.DefaultGroupName ? null : category;
                attributes[name] = string.IsNullOrWhiteSpace(category) ? name : $"{name}(\"{category}\")";
            }
            else
            {
                attributes[name] = $"{name}({Entity.IsVisibleInNavigations.GetString()})";
            }
            namespaces.Add("DevExpress.Persistent.Base");
        }

        if (Entity.IsVisibleInReports != DefaultBoolean.Default)
        {
            var name = "VisibleInReports";
            attributes[name] = $"{name}({Entity.IsVisibleInReports.GetString()})";
            namespaces.Add("DevExpress.Persistent.Base");
        }

        if (Entity.IsVisibleInDashboards != DefaultBoolean.Default)
        {
            var name = "VisibleInDashboards";
            attributes[name] = $"{name}({Entity.IsVisibleInDashboards.GetString()})";
            namespaces.Add("DevExpress.Persistent.Base");
        }

        if (!string.IsNullOrWhiteSpace(Entity.TableName))
        {
            var name = "Persistent";
            attributes[name] = $"{name}(\"{Entity.TableName}\")";
            namespaces.Add("DevExpress.Xpo");
        }

        if (Entity.IsDeferredDeletion != DefaultBoolean.Default)
        {
            var name = "DeferredDeletion";
            attributes[name] = Entity.IsDeferredDeletion == DefaultBoolean.True ? name : $"{name}({Entity.IsDeferredDeletion.GetString()})";
            namespaces.Add("DevExpress.Xpo");
        }

        if (Entity.IsOptimisticLocking != DefaultBoolean.Default)
        {
            var name = "OptimisticLocking";
            attributes[name] = Entity.IsOptimisticLocking == DefaultBoolean.True ? name : $"{name}({Entity.IsOptimisticLocking.GetString()})";
            namespaces.Add("DevExpress.Xpo");
        }

        var rules = Entity.ValidationRules.Where(r => r.TargetProperty == null).ToList();
        foreach (var rule in rules)
        {
            var (name, value) = CreateRuleAttribute(rule);
            if (name != null && value != null)
            {
                attributes[name] = value;
            }
        }

        if (rules.Count != 0)
        {
            namespaces.Add("DevExpress.Persistent.Validation");
        }

        var appearances = Entity.Appearances.Where(a => a.TargetProperty == null).ToList();
        foreach (var appearance in appearances)
        {
            var (name, value) = CreateAppearanceAttribute(appearance, out var appearanceNamespaces);
            if (name != null && value != null)
            {
                attributes[$"{name}-{appearance.AppearanceId}"] = value;
                namespaces.AddRange(appearanceNamespaces);
            }
        }

        if (appearances.Count != 0)
        {
            namespaces.Add("DevExpress.ExpressApp.ConditionalAppearance");
        }

        return attributes;
    }

    private Dictionary<string, string> GetPropertyAttributes(EntityProperty entityProperty, out List<string> namespaces)
    {
        var attributes = new Dictionary<string, string>();
        namespaces = [];

        if (!string.IsNullOrWhiteSpace(entityProperty.Caption))
        {
            var name = "XafDisplayName";
            attributes[name] = $"{name}(\"{entityProperty.Caption}\")";
            namespaces.Add("DevExpress.ExpressApp.DC");
        }

        if (!string.IsNullOrWhiteSpace(entityProperty.Description))
        {
            var name = "Description";
            attributes[name] = $"{name}(\"{entityProperty.Description.ToSingleLine()}\")";
            namespaces.Add("System.ComponentModel");
        }

        if (entityProperty.IsBrowsable != DefaultBoolean.Default)
        {
            var name = "Browsable";
            attributes[name] = entityProperty.IsBrowsable == DefaultBoolean.True ? name : $"{name}({entityProperty.IsBrowsable.GetString()})";
            namespaces.Add("System.ComponentModel");
        }

        if (entityProperty.IsVisibleInDetailView != DefaultBoolean.Default)
        {
            var name = "VisibleInDetailView";
            attributes[name] = $"{name}({entityProperty.IsVisibleInDetailView.GetString()})";
            namespaces.Add("DevExpress.Persistent.Base");
        }

        if (entityProperty.IsVisibleInListView != DefaultBoolean.Default)
        {
            var name = "VisibleInListView";
            attributes[name] = $"{name}({entityProperty.IsVisibleInListView.GetString()})";
            namespaces.Add("DevExpress.Persistent.Base");
        }

        if (entityProperty.IsVisibleInLookupListView != DefaultBoolean.Default)
        {
            var name = "VisibleInLookupListView";
            attributes[name] = $"{name}({entityProperty.IsVisibleInLookupListView.GetString()})";
            namespaces.Add("DevExpress.Persistent.Base");
        }

        if (entityProperty.IsVisibleInReports != DefaultBoolean.Default)
        {
            var name = "VisibleInReports";
            attributes[name] = $"{name}({entityProperty.IsVisibleInReports.GetString()})";
            namespaces.Add("DevExpress.Persistent.Base");
        }

        if (entityProperty.IsPrimaryKey)
        {
            var name = "Key";
            attributes[name] = name;
            namespaces.Add("DevExpress.ExpressApp.Data");
        }

        if (entityProperty.IsImmediatePost != DefaultBoolean.Default)
        {
            var name = "ImmediatePostData";
            attributes[name] = entityProperty.IsImmediatePost == DefaultBoolean.True ? name : $"{name}({entityProperty.IsImmediatePost.GetString()})";
            namespaces.Add("DevExpress.Persistent.Base");
        }

        if (entityProperty.NoForeignKeyConstraints == DefaultBoolean.True)
        {
            var name = "NoForeignKey";
            attributes[name] = name;
            namespaces.Add("DevExpress.Xpo");
        }

        if (entityProperty.IsEditable == DefaultBoolean.False && !entityProperty.IsReadonly)
        {
            var name = "ModelDefault";
            attributes[name] = $"{name}(\"AllowEdit\", \"false\")";
            namespaces.Add("DevExpress.ExpressApp.Model");
        }

        if (entityProperty.IsCloneable == DefaultBoolean.False)
        {
            var name = "NonCloneable";
            attributes[name] = name;
            namespaces.Add("DevExpress.Persistent.Base");
        }

        if (!string.IsNullOrWhiteSpace(entityProperty.ColumnName))
        {
            var name = "Persistent";
            attributes[name] = $"{name}(\"{entityProperty.ColumnName}\")";
            namespaces.Add("DevExpress.Xpo");
        }
        else if (entityProperty.IsPersistent == DefaultBoolean.True)
        {
            var name = "Persistent";
            attributes[name] = name;
            namespaces.Add("DevExpress.Xpo");
        }
        else if (entityProperty.IsPersistent == DefaultBoolean.False)
        {
            var name = "NonPersistent";
            attributes[name] = name;
            namespaces.Add("DevExpress.Xpo");
        }

        if (entityProperty.IsFetchOnly == DefaultBoolean.True)
        {
            var name = "FetchOnly";
            attributes[name] = name;
            namespaces.Add("DevExpress.Xpo");
        }

        if (!string.IsNullOrWhiteSpace(entityProperty.ColumnDefaultValue))
        {
            var name = "ColumnDefaultValue";
            attributes[name] = $"{name}({entityProperty.ColumnDefaultValue})";
            namespaces.Add("DevExpress.Xpo");
        }

        if (!string.IsNullOrWhiteSpace(entityProperty.PropertyEditor))
        {
            var name = "EditorAlias";
            attributes[name] = $"{name}(\"{entityProperty.PropertyEditor}\")";
            namespaces.Add("DevExpress.Persistent.Base");
        }

        if ((entityProperty.PropertyType == PropertyType.Entity || entityProperty.PropertyType == PropertyType.EntityCollection)
            && entityProperty.DataSourceType != DataSourceType.Default && !string.IsNullOrWhiteSpace(entityProperty.DataSource))
        {
            var name = entityProperty.DataSourceType switch
            {
                DataSourceType.DataSourceCriteria => "DataSourceCriteria",
                DataSourceType.DataSourceCriteriaProperty => "DataSourceCriteriaProperty",
                _ => "DataSourceProperty",
            };

            if (entityProperty.DataSourceType == DataSourceType.DataSourceCriteria)
            {
                attributes[name] = $"{name}(\"{entityProperty.DataSource}\")";
            }
            else
            {
                attributes[name] = $"{name}(nameof({entityProperty.DataSource}))";
            }

            namespaces.Add("DevExpress.Persistent.Base");
        }

        var association = GetAssociation(entityProperty);
        if (association != null)
        {
            var name = "Association";

            if (entityProperty.EntityType.DomainModel != Entity.DomainModel)
            {
                attributes[name] = $"{name}(\"{association.Name}\", typeof({entityProperty.EntityType.Name}))";
            }
            else
            {
                attributes[name] = $"{name}(\"{association.Name}\")";

                if (association.IsAggregated && entityProperty.PropertyType == PropertyType.EntityCollection)
                {
                    attributes["Aggregated"] = "DevExpress.Xpo.Aggregated";
                }
            }

            namespaces.Add("DevExpress.Xpo");
        }

        if (entityProperty.PropertyType == PropertyType.String)
        {
            if (entityProperty.StringLength > 0)
            {
                var name = "Size";
                attributes[name] = $"{name}({entityProperty.StringLength})";
                namespaces.Add("DevExpress.Xpo");
            }
        }
        else if (entityProperty.PropertyType == PropertyType.LongString)
        {
            var name = "Size";
            attributes[name] = $"{name}({nameof(SizeAttribute)}.{nameof(SizeAttribute.Unlimited)})";
            namespaces.Add("DevExpress.Xpo");
        }

        var rules = Entity.ValidationRules.Where(r => r.TargetProperty?.Oid == entityProperty.Oid).ToList();
        foreach (var rule in rules)
        {
            var (name, value) = CreateRuleAttribute(rule);
            if (name != null && value != null)
            {
                attributes[name] = value;
            }
        }

        if (rules.Count != 0)
        {
            namespaces.Add("DevExpress.Persistent.Validation");
        }

        var appearances = Entity.Appearances.Where(a => a.TargetProperty?.Oid == entityProperty.Oid).ToList();
        foreach (var appearance in appearances)
        {
            var (name, value) = CreateAppearanceAttribute(appearance, out var appearanceNamespaces);
            if (name != null && value != null)
            {
                attributes[$"{name}-{appearance.AppearanceId}"] = value;
                namespaces.AddRange(appearanceNamespaces);
            }
        }

        if (appearances.Count != 0)
        {
            namespaces.Add("DevExpress.ExpressApp.ConditionalAppearance");
        }

        return attributes;
    }

    private static (string, string) CreateRuleAttribute(ValidationRule rule)
    {
        if (rule is RequiredRule requiredRule)
        {
            return CreateRequiredRuleAttribute(requiredRule);
        }
        else if (rule is UniqueValueRule uniqueValueRule)
        {
            return CreateUniqueValueRuleAttribute(uniqueValueRule);
        }
        else if (rule is RangeRule rangeRule)
        {
            return CreateRangeRuleAttribute(rangeRule);
        }
        else if (rule is FromBoolPropertyRule fromBoolPropertyRule)
        {
            return CreateFromBoolPropertyRuleAttribute(fromBoolPropertyRule);
        }

        return (null, null);
    }

    private static (string, string) CreateRequiredRuleAttribute(RequiredRule rule)
    {
        var attributeName = "RuleRequiredField";
        var name = attributeName;
        if (!string.IsNullOrWhiteSpace(rule.Name) && rule.Name != "None")
        {
            name = $"{attributeName}-{rule.Name}";
        }

        return (name, $"{attributeName}");
    }

    private static (string, string) CreateUniqueValueRuleAttribute(UniqueValueRule rule)
    {
        var attributeName = "RuleUniqueValue";
        var name = attributeName;
        if (!string.IsNullOrWhiteSpace(rule.Name) && rule.Name != "None")
        {
            name = $"{attributeName}-{rule.Name}";
        }

        return (name, $"{attributeName}");
    }

    private static (string, string) CreateRangeRuleAttribute(RangeRule rule)
    {
        var parameters = new List<string>();

        if (!string.IsNullOrWhiteSpace(rule.Name) && rule.Name != "None")
        {
            parameters.Add($"\"{rule.Name}\"");
            parameters.Add(rule.MinValue + string.Empty);
            parameters.Add(rule.MaxValue + string.Empty);
        }
        else
        {
            parameters.Add(rule.MinValue + string.Empty);
            parameters.Add(rule.MaxValue + string.Empty);
        }

        var attributeName = "RuleRange";
        var name = attributeName;
        if (!string.IsNullOrWhiteSpace(rule.Name) && rule.Name != "None")
        {
            name = $"{attributeName}-{rule.Name}";
        }

        return (name, $"{attributeName}({string.Join(", ", parameters)})");
    }

    private static (string, string) CreateFromBoolPropertyRuleAttribute(FromBoolPropertyRule rule)
    {
        var attributeName = "RuleFromBoolProperty";
        var name = attributeName;
        if (!string.IsNullOrWhiteSpace(rule.Name) && rule.Name != "None")
        {
            name = $"{attributeName}-{rule.Name}";
        }

        var parameters = new List<string>();

        if (!string.IsNullOrWhiteSpace(rule.UsedProperties))
        {
            parameters.Add($"UsedProperties = \"{rule.UsedProperties}\"");
        }

        if (!string.IsNullOrWhiteSpace(rule.ErrorMessage))
        {
            parameters.Add($"CustomMessageTemplate = \"{rule.ErrorMessage}\"");
        }

        return (name, $"{attributeName}({string.Join(", ", parameters)})");
    }

    private static (string, string) CreateAppearanceAttribute(Appearance appearance, out List<string> namespaces)
    {
        namespaces = [];

        var name = "Appearance";
        var parameters = new List<string>();

        if (!string.IsNullOrWhiteSpace(appearance.AppearanceItemType))
        {
            parameters.Add($"AppearanceItemType = \"{appearance.AppearanceItemType}\"");
        }

        if (!string.IsNullOrWhiteSpace(appearance.TargetItems))
        {
            parameters.Add($"TargetItems = \"{appearance.TargetItems}\"");
        }

        if (!string.IsNullOrWhiteSpace(appearance.Criteria))
        {
            parameters.Add($"Criteria = \"{appearance.Criteria}\"");
        }

        if (!string.IsNullOrWhiteSpace(appearance.Method))
        {
            parameters.Add($"Method = \"{appearance.Method}\"");
        }

        if (!string.IsNullOrWhiteSpace(appearance.Context))
        {
            parameters.Add($"Context = \"{appearance.Context}\"");
        }

        if (!string.IsNullOrWhiteSpace(appearance.BackColor))
        {
            parameters.Add($"BackColor = \"{appearance.BackColor}\"");
        }

        if (!string.IsNullOrWhiteSpace(appearance.FontColor))
        {
            parameters.Add($"FontColor = \"{appearance.FontColor}\"");
        }

        if (appearance.FontStyle.HasValue)
        {
            namespaces.Add("DevExpress.Drawing");

            var fontStyle = appearance.FontStyle.Value switch
            {
                DXFontStyle.Bold => "DXFontStyle.Bold",
                DXFontStyle.Italic => "DXFontStyle.Italic",
                DXFontStyle.Underline => "DXFontStyle.Underline",
                DXFontStyle.Strikeout => "DXFontStyle.Strikeout",
                _ => "DXFontStyle.Regular",
            };

            parameters.Add($"FontStyle = {fontStyle}");
        }

        if (appearance.Visibility.HasValue)
        {
            namespaces.Add("DevExpress.ExpressApp.Editors");

            var visibility = appearance.Visibility.Value switch
            {
                ViewItemVisibility.Hide => "ViewItemVisibility.Hide",
                ViewItemVisibility.ShowEmptySpace => "ViewItemVisibility.ShowEmptySpace",
                _ => "ViewItemVisibility.Show",
            };

            parameters.Add($"Visibility = {visibility}");
        }

        if (appearance.Enabled.HasValue)
        {
            parameters.Add($"Enabled = {appearance.Enabled.Value}");
        }

        if (appearance.Priority.HasValue)
        {
            parameters.Add($"Priority = {appearance.Priority.Value}");
        }

        return (name, $"{name}(\"{appearance.AppearanceId}\", {string.Join(", ", [.. parameters])})");
    }

    public string GenerateCode()
    {
        var code = new StringBuilder();

        var entityNamespace = Entity.GetNamespace();
        if (!string.IsNullOrWhiteSpace(entityNamespace))
        {
            code.AppendLine(RenderTemplate("Namespace", new { Namespace = entityNamespace }));
            code.AppendLine();
        }

        code.AppendLine(RenderTemplate("TypeSummary", new
        {
            Summary = !string.IsNullOrWhiteSpace(Entity.Caption) ? Entity.Caption : Entity.Name,
            DescriptionLines = Entity.Description.ToLines()
        }));

        var entityAttributes = GetEntityAttributes(out List<string> eaNamespaces);
        foreach (var entityAttribute in entityAttributes)
        {
            var attribute = entityAttribute.Value;
            if (attribute.EndsWith("()"))
            {
                attribute = attribute[..^2];
            }
            code.AppendLine(RenderTemplate("TypeAttribute", new { TypeAttribute = attribute }));
        }

        var namespaces = new List<string>
        {
            "DevExpress.Xpo"
        };

        var baseClassName = Entity.BaseClass?.Name;
        if (string.IsNullOrWhiteSpace(baseClassName))
        {
            baseClassName = "BaseObject";
            namespaces.Add("DevExpress.Persistent.BaseImpl");
        }

        code.AppendLine(RenderTemplate("ClassStatement", new
        {
            ClassName = Entity.Name,
            BaseClassName = baseClassName,
            Entity.IsAbstract
        }));

        if (Entity.BaseClass != null)
        {
            namespaces.Add(Entity.BaseClass.GetNamespace());
        }

        namespaces.AddRange(eaNamespaces);

        code.AppendLine("{");

        var entityProperties = EntityProperties.Where(p => p.PropertyType != PropertyType.Unknown).ToList();

        var normalProperties = entityProperties
            .Where(p => !p.IsReadonly)
            .Where(p => p.PropertyType != PropertyType.EntityCollection)
            .ToList();

        foreach (var normalProperty in normalProperties)
        {
            code.AppendLine(RenderTemplate("PropertyField", new
            {
                PropertyName = normalProperty.Name,
                PropertyType = normalProperty.TypeName
            }));
        }

        if (normalProperties.Count != 0)
        {
            code.AppendLine();
        }

        foreach (var property in entityProperties)
        {
            code.AppendLine(RenderTemplate("MemberSummary", new
            {
                Summary = !string.IsNullOrWhiteSpace(property.Caption) ? property.Caption : property.Name,
                DescriptionLines = property.Description.ToLines()
            }));

            var propertyAttributes = GetPropertyAttributes(property, out List<string> paNamespaces);
            foreach (var propertyAttribute in propertyAttributes)
            {
                var attribute = propertyAttribute.Value;
                if (attribute.EndsWith("()"))
                {
                    attribute = attribute[..^2];
                }
                code.AppendLine(RenderTemplate("MemberAttribute", new { MemberAttribute = attribute }));
            }

            namespaces.AddRange(paNamespaces);
            namespaces.AddRange(GetPropertyTypeNamespaces(property));

            if (property.PropertyType == PropertyType.Entity)
            {
                var association = GetAssociation(property);
                if (association != null && association.AssociationType == AssociationType.OneToOne)
                {
                    code.AppendLine(RenderTemplate("XpoOneToOneProperty", new
                    {
                        PropertyName = property.Name,
                        AssociationPropertyName = association.Parent == property.Entity
                            ? association.ChildPropertyName
                            : association.ParentPropertyName,
                        PropertyType = property.TypeName
                    }));
                }
                else
                {
                    code.AppendLine(RenderTemplate("XpoProperty", new
                    {
                        PropertyName = property.Name,
                        PropertyType = property.TypeName
                    }));
                }
            }
            else if (property.PropertyType == PropertyType.EntityCollection)
            {
                code.AppendLine(RenderTemplate("XPCollectionProperty", new
                {
                    PropertyName = property.Name,
                    EntityType = property.TypeName
                }));
            }
            else if (property.IsReadonly)
            {
                code.AppendLine(RenderTemplate("ReadonlyProperty", new
                {
                    PropertyName = property.Name,
                    PropertyType = property.TypeName
                }));
            }
            else
            {
                code.AppendLine(RenderTemplate("XpoProperty", new
                {
                    PropertyName = property.Name,
                    PropertyType = property.TypeName
                }));
            }

            code.AppendLine();
        }

        var containsDataSourceProperties = entityProperties
            .Where(p => p.DataSourceType != DataSourceType.Default)
            .Where(p => p.DataSourceType != DataSourceType.DataSourceCriteria)
            .Where(p => !string.IsNullOrWhiteSpace(p.DataSource))
            .DistinctBy(p => p.DataSource)
            .ToList();

        foreach (var dataSourceProperty in containsDataSourceProperties)
        {
            code.AppendLine(RenderTemplate("MemberAttribute", new { MemberAttribute = "Browsable(false)" }));
            code.AppendLine(RenderTemplate("ReadonlyProperty", new
            {
                PropertyName = dataSourceProperty.DataSource,
                PropertyType = $"IList<{dataSourceProperty.TypeName}>"
            }));

            code.AppendLine();

            if (!namespaces.Contains("System.ComponentModel"))
            {
                namespaces.Add("System.ComponentModel");
            }
        }

        if (!entityProperties.Any(p => p.Name == "IsValid") && !string.IsNullOrWhiteSpace(Entity.ValidationMessage))
        {
            code.AppendLine(RenderTemplate("MemberAttribute", new { MemberAttribute = "Browsable(false)" }));
            code.AppendLine(RenderTemplate("MemberAttribute", new
            {
                MemberAttribute = $"RuleFromBoolProperty(CustomMessageTemplate = \"{Entity.ValidationMessage}\")"
            }));

            code.AppendLine(RenderTemplate("ReadonlyProperty", new
            {
                PropertyName = "IsValid",
                PropertyType = "bool"
            }));

            code.AppendLine();
        }

        entityProperties.Where(p => p is AssociationProperty).ToList().ForEach(p => p.Entity = null);

        code.AppendLine(RenderTemplate("XpoClassConstructor", new
        {
            ClassName = Entity.Name,
            DefaultValues = entityProperties.Where(p => !string.IsNullOrWhiteSpace(p.DefaultValue)).Select(p => new
            {
                PropertyName = p.Name,
                p.DefaultValue
            })
        }));

        foreach (var readOnlyProperty in entityProperties.Where(p => p.IsReadonly && p.PropertyType != PropertyType.EntityCollection))
        {
            code.AppendLine();
            code.AppendLine(RenderTemplate("ReadonlyPropertyMethod", new
            {
                PropertyName = readOnlyProperty.Name,
                PropertyType = readOnlyProperty.TypeName
            }));
        }

        foreach (var dataSourceProperty in containsDataSourceProperties)
        {
            code.AppendLine();
            code.AppendLine(RenderTemplate("ReadonlyPropertyMethod", new
            {
                PropertyName = dataSourceProperty.DataSource,
                PropertyType = $"IList<{dataSourceProperty.TypeName}>"
            }));
        }

        if (!entityProperties.Any(p => p.Name == "IsValid") && !string.IsNullOrWhiteSpace(Entity.ValidationMessage))
        {
            code.AppendLine();
            code.AppendLine(RenderTemplate("ReadonlyPropertyMethod", new
            {
                PropertyName = "IsValid",
                PropertyType = "bool"
            }));
        }

        code.AppendLine("}");

        namespaces = namespaces
            .Where(n => !string.IsNullOrWhiteSpace(n))
            .Where(n => n != entityNamespace)
            .Distinct()
            .OrderByDescending(n => n)
            .ToList();

        if (namespaces.Count != 0)
        {
            for (var i = 0; i < namespaces.Count; i++)
            {
                if (i == 0)
                {
                    code.Insert(0, "\r\n");
                }
                code.Insert(0, RenderTemplate("UsingNamespace", new { Namespace = namespaces[i] }) + "\r\n");
            }
        }

        return code.ToString();
    }
}
